/**
 * 支持的事件类型
 */

import {TrackerQueue} from './TrackerQueue.js'
import {findTrackNode, getDynamicParams, getPerformanceInfo, getQuery} from './TrackerUtils.js'

/**
 * query 参数最大长度
 */
const maxPageParamsMaxLength = 20

/**
 * 自动化埋点支持的事件
 */
export const TrackEvent = {
  pageView: 'pageview', // 页面曝光
  pageLeave: 'pageleave', // 离开页面
  click: 'click', // 元素点击
  scroll: 'scroll', // 页面滚动
  areaVisit: 'areavisit', // 元素区域访问，点击区域时触发 entry，离开区域时触发 leave，记录访问时长
  elementView: 'elementview' // 元素曝光，曝光时触发一次 view，隐藏时触发 leave，记录曝光时长
}

/**
 * 后端接收的事件名
 */
export const TrackEventBackend = {
  pageView: 'page_view', // 页面曝光
  pageLoad: 'page_load', // 页面加载完毕
  pageLeave: 'page_leave', // 离开页面
  pageScroll: 'page_scroll', // 页面滚动
  pageActiveStop: 'page_active_stop', // 页面停止活动
  pageActiveRestart: 'page_active_restart', // 页面重新开始活动
  areaVisitEntry: 'area_visit_entry', // 元素区域访问 entry
  areaVisitLeave: 'area_visit_leave', // 元素区域访问 leave
  elementView: 'element_view', // 元素曝光
  elementHide: 'element_hide', // 元素隐藏
  abnormalDevice: 'abnormal_device', // 异常设备
  click: 'click' // 点击
}

/**
 * 埋点类
 * 初始化时需要异步获取数据
 */
export class Tracker {
  // 基础信息
  basicParams = {
    user_info: {},
    device_info: {}
  }
  customParams = {} // 自定义信息
  customFnMap = {} // 获取自定义参数方法
  trackerQueue = null // 埋点数据队列，定时上报
  areaVisitEntryMap = {} // 元素区域访问 map
  elementViewMap = {} // 元素曝光 map
  counterMap = {}
  counterKey = 'count'
  elementViewobserver = null
  destroyFlag = false // 是否已销毁
  options = {}
  pageLoadParams = null // pageLoad 事件外部数据
  performanceInfo = null // 性能相关数据
  scrollInfo = {
    timer: null,
    flag: true,
    scrollTop: 0, // 上次滚动高度
    duration: 100, // 100ms 内无后续滚动事件时，认为完成了一次滚动
    startTime: 0
  }
  _canSendFlag = true // 是否允许上报
  pageActiveListenerTimer = null // 页面活动状态定时器
  pageActiveListenerInterval = 10000 // 页面活动状态时间间隔

  get canSendFlag() {
    return this._canSendFlag
  }

  set canSendFlag(value) {
    if (value !== this._canSendFlag) {
      if (value) {
        console.log('恢复数据上报')
        this._canSendFlag = value

        this.trackEvent(TrackEventBackend.pageActiveRestart)
      } else {
        console.log('长时间未操作，停止数据上报')
        this.trackEvent(TrackEventBackend.pageActiveStop)
      }
    }

    this._canSendFlag = value
  }

  /**
   * 埋点
   * @param {*} options // 配置
   * @param {*} options.customParams // 通用埋点数据
   * @param {*} options.customFnMap // 获取自定义参数方法
   * @param {*} options.scroll // 是否监听 scroll 事件
   * @param {*} options.pageLoadDelayFlag // 是否在页面中手动调用 trackPageLoadEvent 方法，不设置时会多次触发 page_load 事件
   * @param {*} options.pure // 不触发和监听任何事件
   */
  constructor(pageType, options = {}) {
    this.basicParams.page_type = pageType
    this.options = options
    this.setCustomFnMap(options.customFnMap)
    this.setCustomParams(options.customParams)
    this.init()
  }

  async init() {
    // 初始化数据队列
    this.initTrackerQueue()
    // 初始化基础信息
    this.initBasicParams()
    await this.initBasicParamsAsync()

    if (!this.options.pure) {
      // 初始化事件监听器
      this.initEvent()
      // 初始化计数器
      this.initCounter()
    }
  }

  /**
   * 刷新埋点对象
   * vue 组件中，可能会在 mounted 之前创建 tracker 实例，这时未挂载的元素 tracker 无法检测
   */
  refresh() {
    this.initCounter()
    this.initListenerElementView()
  }

  // 设置 customParams
  setCustomParams(customParams = {}) {
    this.customParams = customParams
  }

  // 设置 customFnMap
  setCustomFnMap(customFnMap = {}) {
    this.customFnMap = customFnMap
  }

  // 初始化计数器
  initCounter() {
    const elements = document.querySelectorAll('[data-track-counter]')

    elements.forEach(element => {
      const trackCounter = element.getAttribute('data-track-counter')

      trackCounter.split(',').forEach(eventType => {
        const elementName = element.getAttribute(`data-track-${eventType}`)

        if (elementName) {
          this.addCounter(elementName, eventType)
        }
      })
    })
  }

  /**
   * 新增计数器
   * @param {*} elementName
   * @param {*} eventType
   */
  addCounter(elementName, eventType) {
    // 支持计数器的事件
    const counterEventMap = {
      [TrackEvent.areaVisit]: TrackEventBackend.areaVisitEntry,
      [TrackEvent.elementView]: TrackEventBackend.elementView,
      [TrackEvent.click]: TrackEventBackend.click
    }
    // 优先转换为通用事件，无法转换时使用原始 eventType 以支持自定义事件
    const backendEventType = counterEventMap[eventType] || eventType
    const key = `${elementName}-${backendEventType}`

    if (this.counterMap[key] === undefined) {
      this.counterMap[key] = 0
    }
  }

  // 初始化数据队列
  initTrackerQueue() {
    this.trackerQueue = new TrackerQueue({
      send: this.send.bind(this)
    })
  }

  // 初始化基础数据
  initBasicParams() {
    // 页面信息
    const query = getQuery()

    for (let prop in query) {
      if (query[prop].length > maxPageParamsMaxLength) {
        query[prop] = query[prop].slice(0, maxPageParamsMaxLength) + '...'
      }
    }

    this.basicParams['page_url'] = window.location.origin + window.location.pathname
    this.basicParams['page_params'] = query
  }

  // 初始化需异步获取的基础数据
  async initBasicParamsAsync() {
    // const userInfo = await UserService.getUserInfo()

    // // 用户信息
    // if (userInfo) {
    //   this.basicParams['customer_id'] = userInfo.openId
    // }
  }

  // 初始化事件
  initEvent() {
    // 触发 pv 事件
    this.trackPageViewEvent()
    // 事件委托-click
    window.addEventListener('click', this.handleClick.bind(this), true)
    // 事件委托-beforeunload
    window.addEventListener('beforeunload', this.handleBeforeunload.bind(this), true)
    // 初始化性能监控事件
    this.initPerformanceEvent()
    // 初始化元素曝光事件
    this.initListenerElementView()
    // 事件委托-scroll
    window.addEventListener('scroll', this.handleScroll.bind(this), true)
  }

  // 初始化页面活动状态
  initPageActiveListener() {
    this.startPageActiveListener()

    document.addEventListener('visibilitychange', () => {
      // 页面隐藏时停止上报
      this.canSendFlag = !document.hidden

      if (document.hidden) {
        this.stopPageActiveListener()
      } else {
        this.startPageActiveListener()
      }
    })
  }

  startPageActiveListener() {
    this.stopPageActiveListener()

    this.canSendFlag = true
    this.pageActiveListenerTimer = setTimeout(() => {
      this.canSendFlag = false
    }, this.pageActiveListenerInterval)
  }

  stopPageActiveListener() {
    clearTimeout(this.pageActiveListenerTimer)
  }

  // 初始化性能监控事件
  initPerformanceEvent() {
    window.addEventListener('load', () => {
      this.trackPageLoadEvent(null, true)
      this.initPageActiveListener()
    })
  }

  // 监听元素曝光事件
  initListenerElementView() {
    if (IntersectionObserver) {
      const nodes = document.querySelectorAll('[data-track-elementview]')

      if (this.elementViewobserver) {
        // 多次触发时，先销毁之前的观察对象
        this.elementViewobserver.disconnect()
      }
      // 创建一个 Intersection Observer 实例
      const observer = new IntersectionObserver(this.handleElementView.bind(this))

      nodes.forEach(element => {
        observer.observe(element)
      })

      this.elementViewobserver = observer
    } else {
      console.error('elementView Error：不支持 IntersectionObserver')
    }
  }

  // 元素曝光处理函数
  handleElementView(entries, observer) {
    entries.forEach(async entry => {
      const trackData = await this.getTrackData(entry.target, TrackEvent.elementView)
      const elementName = trackData.elementName
      if (trackData) {
        if (entry.isIntersecting) {
          this.trackElementViewEvent(elementName, trackData.params)
        } else if (this.elementViewMap[elementName]) {
          this.trackElementHideEvent(elementName, trackData.params)
        }
      }
    })
  }

  // 页面离开事件处理函数
  async handleBeforeunload(e) {
    this.destroyFlag = true
    this.trackEvent(TrackEventBackend.pageLeave)
    this.trackerQueue?.destroy()
    this.elementViewobserver?.disconnect()
  }

  // 滚动事件处理函数
  async handleScroll(event) {
    this.startPageActiveListener()

    if (!this.options.scroll) {
      return
    }

    clearTimeout(this.scrollInfo.timer)

    this.scrollInfo.timer = setTimeout(() => {
      const params = {
        scrollTop: window.scrollY,
        distance: Math.abs(this.scrollInfo.scrollTop - window.scrollY),
        direction: this.scrollInfo.scrollTop - window.scrollY > 0 ? 'top' : 'bottom',
        duration: Date.now() - this.scrollInfo.startTime
      }

      this.trackEvent(TrackEventBackend.pageScroll, null, params)

      this.scrollInfo.scrollTop = window.scrollY
      this.scrollInfo.flag = true
    }, this.scrollInfo.duration)

    if (this.scrollInfo.flag) {
      this.scrollInfo.flag = false
      this.scrollInfo.startTime = Date.now()
    }
  }

  // 处理点击事件处理函数
  async handleClick(event) {
    const target = event.target

    this.startPageActiveListener()

    // click 事件
    // 向上查找最近的带有埋点属性的元素
    const trackClickNode = findTrackNode(target, TrackEvent.click)

    if (trackClickNode) {
      const trackData = await this.getTrackData(trackClickNode, TrackEvent.click)
      if (trackData) {
        this.trackClickEvent(trackData.elementName, trackData.params)
      }
    }

    // areaVisit 事件
    const trackAreavisitNode = findTrackNode(target, TrackEvent.areaVisit)
    let trackAreavisitCurrentElementName = ''

    if (trackAreavisitNode) {
      const trackData = await this.getTrackData(trackAreavisitNode, TrackEvent.areaVisit)
      trackAreavisitCurrentElementName = trackData.elementName

      // areaVisitEntry 事件
      if (trackData && !this.areaVisitEntryMap[trackData.elementName]) {
        // 不存在进入事件
        trackData.timestamp = Date.now()
        this.areaVisitEntryMap[trackData.elementName] = trackData
        this.trackEvent(TrackEventBackend.areaVisitEntry, trackData.elementName, trackData.params)
      }
    }

    // areaVisitLeave 事件
    if (Object.keys(this.areaVisitEntryMap).length > 0) {
      for (let prop in this.areaVisitEntryMap) {
        if (prop !== trackAreavisitCurrentElementName) {
          // 在区域内的点击不会触发 leave 事件
          const trackData = this.areaVisitEntryMap[prop]
          this.areaVisitEntryMap[trackData.elementName] = trackData
          trackData.params.duration = Date.now() - trackData.timestamp
          this.trackEvent(TrackEventBackend.areaVisitLeave, trackData.elementName, trackData.params)
          delete this.areaVisitEntryMap[prop]
        }
      }
    }
  }

  // 获取埋点数据
  async getTrackData(element, type) {
    try {
      const elementName = element.getAttribute(`data-track-${type}`)

      // 处理动态参数
      const dynamicParams = getDynamicParams(element)
      const customFnParams = await this.getCustomFnParams(element)

      return {
        elementName,
        params: {
          ...dynamicParams,
          ...customFnParams
        }
      }
    } catch (error) {
      console.error('获取埋点数据失败 getTrackData:', error)
      return null
    }
  }

  // 获取计数器参数
  getCounterParams(elementName, eventType) {
    const params = {}
    const key = `${elementName}-${eventType}`

    if (this.counterMap[key] !== undefined) {
      params[this.counterKey] = ++this.counterMap[key]
    }
    return params
  }

  // 获取自定义参数（通过函数）
  async getCustomFnParams(element) {
    const customFn = element.getAttribute('data-track-fn')
    if (
      customFn &&
      this.customFnMap &&
      this.customFnMap[customFn] &&
      typeof this.customFnMap[customFn] === 'function'
    ) {
      try {
        return await this.customFnMap[customFn](element)
      } catch (error) {
        console.error(`获取自定义参数失败：${customFn}`, error)
        return {}
      }
    } else if (customFn) {
      console.error(`未找到获取自定义参数方法：${customFn}`)
    }
    return {}
  }

  // --------------------------------------------- 触发上报事件 ---------------------------------------------
  // 触发元素曝光事件
  trackElementViewEvent(elementName, eventData = {}) {
    this.elementViewMap[elementName] = {
      timestamp: Date.now()
    }
    this.trackEvent(TrackEventBackend.elementView, elementName, eventData)
  }

  // 触发元素隐藏事件
  trackElementHideEvent(elementName, eventData = {}) {
    const info = this.elementViewMap[elementName]
    // 元素曝光后才允许触发隐藏事件
    eventData.duration = Date.now() - info.timestamp
    this.trackEvent(TrackEventBackend.elementHide, elementName, eventData)
    delete this.elementViewMap[elementName]
  }

  // 触发点击事件
  trackClickEvent(elementName, eventData = {}) {
    this.trackEvent(TrackEventBackend.click, elementName, eventData)
  }

  // 触发 pv 事件
  trackPageViewEvent() {
    this.trackEvent(TrackEventBackend.pageView)
  }

  /**
   *  触发 pageLoad 事件
   *  外部调用时，需要配置 pageLoadDelayFlag 确保该方法在 onload 事件之后触发
   *  @param {*} eventData
   *  @param {*} isLoad
   */
  trackPageLoadEvent(eventData = {}, isLoad = false) {
    if (isLoad) {
      // 由 onLoad 触发
      this.performanceInfo = getPerformanceInfo()

      if (this.pageLoadParams || !this.options.pageLoadDelayFlag) {
        // 已被外部触发过 || 无需等待外部触发
        this.trackPageLoadEventInner()
      }
    } else {
      // 外部触发
      this.pageLoadParams = Object.assign({}, eventData, {
        performance: {
          page_load: Date.now() - performance.timing.navigationStart
        }
      })

      if (this.performanceInfo) {
        // onload 已触发
        this.trackPageLoadEventInner()
      }
    }
  }

  trackPageLoadEventInner() {
    const params = Object.assign({}, this.pageLoadParams, {
      performance: Object.assign({}, this.performanceInfo, this.pageLoadParams?.performance)
    })

    this.trackEvent(TrackEventBackend.pageLoad, '', params)
  }

  trackAbnormalDevice(msg) {
    this.trackEvent(TrackEventBackend.abnormalDevice, '', {
      err_msg: msg
    })
  }

  trackSubmitEvent(elementName, result, errMsg, isBackend = false) {
    this.trackClickEvent(elementName, {
      result,
      err_msg: errMsg,
      err_msg_from: isBackend ? 'backend' : 'frontend'
    })
  }

  /**
   * 触发上报通用方法
   * @param {*} elementName
   * @param {*} eventData
   */
  trackEvent(eventType, elementName, eventData = {}) {
    const counterParams = this.getCounterParams(elementName, eventType)
    const data = {
      event_type: eventType,
      element_name: elementName,
      ...this.customParams,
      // ...eventData,
      ...this.basicParams,
      ext_params: Object.assign({}, eventData, counterParams)
    }

    if (this.canSendFlag || [TrackEventBackend.pageLeave, TrackEventBackend.pageActiveStop].includes(eventType)) {
      this.trackerQueue.push(data)
    }
  }
  // 发送埋点数据
  async send(data) {
    // 调用数据上报接口
    console.table(
      data.map(item => ({
        element_name: item.element_name,
        event_type: item.event_type,
        ext_params: JSON.stringify(item.ext_params)
      })),
      ['element_name', 'event_type', 'ext_params']
    )
    console.log('埋点数据上报：（索引）', data)

    let requestUrl = `xxxx`


    if ((this.destroyFlag || !this.canSendFlag) && typeof navigator.sendBeacon === 'function') {
      // leave 事件用 navigator.sendBeacon 发送
      const jsonData = JSON.stringify(data)
      const blob = new Blob([jsonData], {type: 'application/json'})

      const result = navigator.sendBeacon(requestUrl, blob)

      console.log('navigator.sendBeacon 数据上报', result, data)
      return
    }

    // Http.post({
    //   url: requestUrl,
    //   headers: {
    //     // post 请求需指定 content-type
    //     'content-type': 'application/json;charset=utf-8'
    //   },
    //   data: JSON.stringify(data)
    // })
  }
}
